package TrafficControlSystem.AlternativeLights;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Observable;

import TrafficControlSystem.AlternativeLights.CrossingLights.State;
import TrafficControlSystem.AlternativeLights.LightsController.Mode;
import TrafficControlSystem.Common.GETINTENSITYRequest;
import TrafficControlSystem.Common.GETINTENSITYResponse;
import TrafficControlSystem.Common.IntensityInfo;
import TrafficControlSystem.Common.IntensityInfo.Direction;
import TrafficControlSystem.Common.LightsInfo;
import TrafficControlSystem.Common.LightsInfo.LightsState;
import TrafficControlSystem.Common.OKResponse;
import TrafficControlSystem.Common.Response;
import TrafficControlSystem.Common.SETLIGHTSRequest;


public class LightsController extends Observable {

	public enum Mode{
		MANUAL,		// manualne sterowanie swiatlami
		AUTOMATIC	// automatyczne sterowanie swiatlami
	};
	
	// Socket na ktorym prowadzone bedzie polaczenie
	private Socket sock = null;

	// Flaga - czy jest wlasnie prowadzona kontrola
	// automatyczna, czy tez manualna
	private Mode mode = Mode.MANUAL;
	
	// Zarzadzane przez kontrolera swiatla
	private CrossingLights[] lights;
	
	// Informacje o natezeniu ruchu
	private IntensityInfo intensityInfo;
	
	// Domyslny serwer
	private String defaultHost = "127.0.0.1";
	private int defaultPort = 5002;
	
	// Watek autopilota
	private ServerCommunicationThread communicationThread;
	
	
	/**
	 * Ustawia domyslny serwer
	 * @param host
	 * @param port
	 */
	public void setDefaulServer(String host, int port){
		defaultHost = host;
		defaultPort = port;
	}
	
	/**
	 * Zwraca domyslnego hosta
	 * @return
	 */
	public String getDefaultHost(){
		return defaultHost;
	}
	
	/**
	 * Zwraca domyslny port serwera
	 * @return
	 */
	public int getDefaultPort() {
		return defaultPort;
	}


	/**
	 * Tworzy kontroler swiatel
	 */
	public LightsController(int lightsCount) {
		
		super();

		communicationThread = new ServerCommunicationThread();
		
		// Stworzenie swiatel do zarzadzania
		lights = new CrossingLights[lightsCount];
		for(int i=0; i<lightsCount; i++)
			lights[i] = new CrossingLights();
		
	}
	
	/**
	 * Laczy z serwerem
	 * Powiadamia obserwatorow, jesli polaczenie sie powiedzie
	 * @returns true, w przypadku poprawnego ustanowienia polaczenia
	 * 			false, wpp
	 */
	public boolean connect(String host, int port){
		
		try {
			
			sock = new Socket(Inet4Address.getByName(host), port);
			sock.setKeepAlive(true);
			
			Thread th = new Thread(communicationThread);
			th.start();
			
			setChanged();
			notifyObservers();
			
			
		} catch (IOException e) {
			System.err.println("Nie udalo sie polaczyc z serwerem.");
			return false;
		}
		
		return true;
		
	}

	/**
	 * Laczy sie do domyslnego serwera
	 * Powiadamia obserwatorow, jesli polaczenie sie powiedzie
	 */
	public boolean connectToDefaultServer() {
		return connect(defaultHost, defaultPort);
	}
	
	
	/**
	 * @return
	 * true, jesli polaczenie z serwerem zostalo ustanowione i trwa
	 * false, wpp
	 */
	public boolean isConnected(){
		if(sock==null)
			return false;
		return sock.isConnected();
	}
	
	
	/**
	 * Ustawia tryb sterowania swiatlami
	 * Powiadamia obserwatorow
	 */
	public synchronized void setMode(Mode mode)
	{
		if(mode == Mode.MANUAL)
		{
			if(this.mode != Mode.MANUAL)
				setManualMode();
		}
		else
		{
			if(this.mode != Mode.AUTOMATIC)
				setAutomaticMode();
		}
		
		setChanged();
		notifyObservers();
		
		
	}
	
	public synchronized boolean isAutomatic(){
		return mode==Mode.AUTOMATIC;
	}
	
	
	private void setManualMode(){
		
		mode = Mode.MANUAL;
	
	}
	
	private void setAutomaticMode(){
		
		mode = Mode.AUTOMATIC;
		
		for(CrossingLights l: lights)
			l.reset();
		
	}

	
	/**
	 * Pobiera intensity z serwera
	 * Aktualizuje swiatla (jesli tryb automatyczny)
	 * Wysyla do serwera nowe swiatla
	 */
	public void updateLights(){
		
		if(sock==null)
			throw new IllegalStateException("Socket niezainicjalizowany");
		
		if(!sock.isConnected())
			throw new IllegalStateException("Wysylanie na niepodlaczony socket");

		// Pobranie intensywnosci ruchu
		//System.out.println("GETINTENSITY...");
		
		GETINTENSITYRequest getIntensityReq = new GETINTENSITYRequest(sock);
		getIntensityReq.send();
		
		GETINTENSITYResponse intensity = (GETINTENSITYResponse) Response.getFrom(sock);
		
		IntensityInfo info = intensity.getIntensity();
		
		if(info==null){
		//	System.out.println("NULL INTENSITY RECEIVED");
		} else {
			
			//System.out.println(info.toString());
			
			for(int i=0; i<lights.length; i++){
				int v = info.getIntensity(i, Direction.VERTICAL);
				int h = info.getIntensity(i, Direction.HORIZONTAL);
				lights[i].setTrafficDensity(v, h);
			}
			
			this.intensityInfo = info;
		}
		
		//System.out.println(" ...dostalem");
		
		if(isAutomatic()){
			for(CrossingLights l: lights){
				l.tick();
			}
			setChanged();
			notifyObservers();
		}
		
		// Wyslanie nowych swiatel
		LightsInfo lightsInfo = new LightsInfo();
		for(int i=0; i<lights.length; i++)
			lightsInfo.setState(i, (lights[i].getHorizontalState()==State.GREEN) ? LightsState.HORIZONTALLY_GREEN: LightsState.HORIZONTALLY_RED);
		
		SETLIGHTSRequest setLightsRequest = new SETLIGHTSRequest(sock, lightsInfo);
		
		//System.out.println("Wysylam swiatla...");
		setLightsRequest.send();
		
		
		// Odebranie potwierdzenia przyjecia nowych swiatel
		OKResponse okResponse = (OKResponse) Response.getFrom(sock);
		
		//System.out.println("  ...Potwierdzono");
		
	}
	
	

	/**
	 * Wywolywane dla recznej zmiany swiatel
	 * Powiadamia obserwatorow
	 * @param id
	 */
	public void changeLightsOnCrossing(int id) {
		lights[id].change();
		//updateLights();
		setChanged();
		notifyObservers();
	}
	
	/**
	 * Zwraca stan swiatel na podanym skrzyzowaniu
	 * @param crossingID
	 * @return
	 */
	public LightsState getLightsState(int crossingID){
		return (lights[crossingID].getHorizontalState()==State.GREEN) ? LightsState.HORIZONTALLY_GREEN: LightsState.HORIZONTALLY_RED;
	}

	/**
	 * Zwraca info, czy dostepna jset informacja o gestosci ruchu
	 * @return
	 */
	public boolean hasIntensityInfo() {
		return intensityInfo != null;
	}
	
	/**
	 * Zwraca intensywnosc ruchu na danym skrzyzowaniu w dana strone
	 */
	public int getIntensity(int crossingID, IntensityInfo.Direction direction) {
		return intensityInfo.getIntensity(crossingID, direction);
	}

	/**
	 * Zwraca aktualny tryb sterowania
	 * @return
	 */
	public Mode getMode() {
		return this.mode;
	}

	

	private class ServerCommunicationThread implements Runnable {

		static final private int TICK_TIME = 250;
		
		private volatile boolean alive = true;

		public ServerCommunicationThread(){
			super();
		}

		public synchronized void markToStop(){
			alive = false;
		}
		
		private synchronized boolean isAlive(){
			return alive;
		}
		
		
		@Override
		public void run() {

			System.out.println("Start komunikacji");
			
			while(isAlive()){
				LightsController.this.updateLights();
				try {
					Thread.sleep(TICK_TIME);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

			System.out.println("Stop komunikacji");
			
		}
		
		
	}

	
	
	
	
	
}






